﻿using Microsoft.Office.Server.Search.Administration;
using Microsoft.Office.Server.Search.Administration.Query;
using Microsoft.Office.Server.Search.Query;
using Microsoft.Office.Server.Search.Query.Rules;
using SPSDeploy.Extensions.Controllers;
using SPSDeploy.Extensions.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace SPSDeploy.Extensions.Modules.Compare
{
    /// <summary>
    /// Search compare module
    /// </summary>
    internal class SearchModule : BaseModule
    {

        /// <summary>
        /// Initialization
        /// </summary>
        /// <param name="controller"></param>
        internal SearchModule(BaseController controller)
            : base(controller)
        {
        }

        /// <summary>
        /// Execute module
        /// </summary>
        /// <param name="compare"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        internal override XElement Execute(XElement compare, XElement parent)
        {
            XElement application = new XElement("serviceApplication", new XAttribute("name", compare.Attribute("name").Value), new XAttribute("type", compare.Attribute("type").Value));
            SearchServiceApplication serviceApplication = (SearchServiceApplication)ServiceApplicationExtension.GetServiceApplication(compare.Attribute("name").Value, compare.Attribute("type").Value);
            if (serviceApplication != null)
            {
                SearchService service = Microsoft.Office.Server.Search.Administration.SearchService.Service;
                Content content = new Content(serviceApplication);
                Ranking ranking = new Ranking(serviceApplication, SearchObjectOwner.EmptyOwner);
                FederationManager manager = new FederationManager(serviceApplication);
                XElement search = (XElement)compare.FirstNode;
                XElement result = CompareSearchApplication(search, serviceApplication, service, content, ranking);
                CompareSearchProxy(search, result, service);
                CompareContentSources(search, result, content);
                CompareCrawlRules(search, result, content);
                CompareCrawlMappings(search, result, content);
                CompareFileTypes(search, result, content);
                CompareCrawlerImpactRules(search, result, service);
                CompareAuthoritativePages(search, result, ranking);
                CompareDemotedSites(search, result, ranking);
                CompareQuerySuggestions(search, result, serviceApplication);
                CompareQueryClientTypes(search, result, serviceApplication);
                CompareResultSources(search, result, serviceApplication, manager);
                CompareResultTypes(search, result, ResultTypesExtension.GetOwners(serviceApplication), manager, new ResultItemTypeManager(serviceApplication));
                CompareQueryRules(search, result, QueryRulesExtension.GetOwners(serviceApplication), manager, new QueryRuleManager(serviceApplication));
                CompareManagedProperties(search, result, serviceApplication);
                Tools.RollupElement(application, result);
                controller.OnProgressMessage(string.Format("Search service application '{0}' information compared.", compare.Attribute("name").Value));
            }
            Tools.RollupElement(parent, application);
            return application;
        }

        #region Search Application
        /// <summary>
        /// Compare search application
        /// </summary>
        /// <param name="search"></param>
        /// <param name="application"></param>
        /// <param name="service"></param>
        /// <param name="content"></param>
        /// <param name="ranking"></param>
        /// <returns></returns>
        private XElement CompareSearchApplication(XElement search, SearchServiceApplication application, SearchService service, Content content, Ranking ranking)
        {
            XElement result = new XElement("searchServiceApplication");
            XElement updates = GetUpdatesElement();
            Compare(updates, search.Attribute("defaultContentAccessAccount"), content.DefaultGatheringAccount);
            Compare(updates, search.Attribute("contactEmail"), service.ContactEmail);
            Compare(updates, search.Attribute("connectionTimeout"), service.ConnectionTimeout);
            Compare(updates, search.Attribute("acknowledgementTimeout"), service.AcknowledgementTimeout);
            Compare(updates, search.Attribute("ignoreSSLWarnings"), service.IgnoreSSLWarnings);
            Compare(updates, search.Attribute("alertsEnabled"), application.AlertsEnabled);
            Compare(updates, search.Attribute("queryLoggingEnabled"), application.QueryLogSettings.QLogEnabled);
            Compare(updates, search.Attribute("searchCenterUrl"), application.SearchCenterUrl ?? string.Empty);
            Compare(updates, search.Attribute("querySuggestionsEnabled"), ranking.QuerySuggestionsEnabled);
            Tools.RollupElement(result, updates);
            return result;
        }
        #endregion

        #region Search Proxy
        /// <summary>
        /// Compare search proxy
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="service"></param>
        private void CompareSearchProxy(XElement search, XElement parent, SearchService service)
        {
            XElement element = new XElement("proxy");
            XElement updates = GetUpdatesElement();
            XElement compare = search.Descendants("proxy").First();
            Compare(updates, compare.Attribute("type"), service.ProxyType.ToString());
            Compare(updates, compare.Attribute("address"), service.WebProxy.Address != null ? service.WebProxy.Address.ToString() : string.Empty);
            Compare(updates, compare.Attribute("bypassOnLocal"), service.WebProxy.BypassProxyOnLocal);
            Compare(updates, compare.Attribute("bypassList"), Tools.GetBypassListText(service.WebProxy.BypassList));
            Compare(updates, compare.Attribute("useForFederation"), service.UseCrawlProxyForFederation);
            Tools.RollupElement(element, updates);
            Tools.RollupElement(parent, element);
        }
        #endregion

        #region Content Sources
        /// <summary>
        /// Compare content sources
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void CompareContentSources(XElement search, XElement parent, Content content)
        {
            XElement elements = new XElement("contentSources");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement itemElement;
            XElement compare = search.Descendants("contentSources").First();
            foreach (ContentSource item in content.ContentSources)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(e => e.Attribute("name").Value == item.Name).FirstOrDefault()) == null)
                    deletes.Add(new XElement("contentSource", new XAttribute("name", item.Name)));
                else if (item is SharePointContentSource && itemElement.Attribute("crawlBehavior").Value != ((SharePointContentSource)item).SharePointCrawlBehavior.ToString())
                {
                    deletes.Add(new XElement("contentSource", new XAttribute("name", item.Name)));
                    adds.Add(itemElement);
                }
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("contentSource", new XAttribute("name", item.Name));
                    Compare(elementUpdates, itemElement.Attribute("crawlPriority"), item.CrawlPriority.ToString());
                    CompareContentSourceSchedule(elementUpdates, itemElement.Descendants("incrementalCrawlSchedule").First(), item.IncrementalCrawlSchedule);
                    CompareContentSourceSchedule(elementUpdates, itemElement.Descendants("fullCrawlSchedule").First(), item.FullCrawlSchedule);
                    if (item is SharePointContentSource)
                    {
                        Compare(elementUpdates, itemElement.Attribute("continuousCrawl"), ((SharePointContentSource)item).EnableContinuousCrawls);
                        Compare(elementUpdates, itemElement.Attribute("followDirectories"), ((SharePointContentSource)item).FollowDirectories);
                    }
                    else if (item is WebContentSource)
                    {
                        Compare(elementUpdates, itemElement.Attribute("maxPageDepth"), ((WebContentSource)item).MaxPageEnumerationDepth);
                        Compare(elementUpdates, itemElement.Attribute("maxSiteDepth"), ((WebContentSource)item).MaxSiteEnumerationDepth);
                    }
                    else if (item is FileShareContentSource)
                        Compare(elementUpdates, itemElement.Attribute("followDirectories"), ((FileShareContentSource)item).FollowDirectories);
                    else if (item is ExchangePublicFolderContentSource)
                        Compare(elementUpdates, itemElement.Attribute("followDirectories"), ((ExchangePublicFolderContentSource)item).FollowDirectories);
                    else if (item is CustomContentSource)
                    {
                        Compare(elementUpdates, itemElement.Attribute("followDirectories"), ((CustomContentSource)item).FollowDirectories);
                        Compare(elementUpdates, itemElement.Attribute("maxPageDepth"), ((CustomContentSource)item).MaxPageEnumerationDepth);
                        Compare(elementUpdates, itemElement.Attribute("maxSiteDepth"), ((CustomContentSource)item).MaxSiteEnumerationDepth);
                    }
                    CompareContentSourceStartAddresses(elementUpdates, itemElement.Descendants("startAddresses").First(), item.StartAddresses);
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (!content.ContentSources.Exists(element.Attribute("name").Value))
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Compare content source schedule
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="scheduleElement"></param>
        /// <param name="schedule"></param>
        private void CompareContentSourceSchedule(XElement updates, XElement scheduleElement, Schedule schedule)
        {
            string description = schedule != null ? schedule.Description : "None";
            if (scheduleElement.Attribute("description").Value != description)
            {
                XElement update = GetUpdateElement(scheduleElement.Name, description, scheduleElement.Attribute("description").Value);
                update.Add(scheduleElement);
                updates.Add(update);
            }
        }

        /// <summary>
        /// Compare content source start addresses
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="startAddressesElement"></param>
        /// <param name="startAddresses"></param>
        private void CompareContentSourceStartAddresses(XElement updates, XElement startAddressesElement, StartAddressCollection startAddresses)
        {
            StringBuilder elementText = new StringBuilder();
            foreach (XElement element in startAddressesElement.Nodes())
                elementText.AppendFormat("{0};", element.Value);
            StringBuilder text = new StringBuilder();
            foreach (Uri item in startAddresses)
                text.AppendFormat("{0};", item.ToString());
            AddUpdateElement(updates, startAddressesElement, text, elementText);
        }
        #endregion

        #region Crawl Rules
        /// <summary>
        /// Compare crawl rules
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void CompareCrawlRules(XElement search, XElement parent, Content content)
        {
            XElement elements = new XElement("crawlRules");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement itemElement;
            XElement compare = search.Descendants("crawlRules").First();
            foreach (CrawlRule item in content.CrawlRules)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(e => e.Attribute("path").Value == item.Path).FirstOrDefault()) == null)
                    deletes.Add(new XElement("crawlRule", new XAttribute("path", item.Path)));
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("crawlRule", new XAttribute("path", item.Path));
                    Compare(elementUpdates, itemElement.Attribute("type"), item.Type.ToString());
                    Compare(elementUpdates, itemElement.Attribute("regularExpression"), item.IsAdvancedRegularExpression);
                    Compare(elementUpdates, itemElement.Attribute("suppressIndexing"), item.SuppressIndexing);
                    Compare(elementUpdates, itemElement.Attribute("followComplexUrls"), item.FollowComplexUrls);
                    Compare(elementUpdates, itemElement.Attribute("crawlAsHttp"), item.CrawlAsHttp);
                    Compare(elementUpdates, itemElement.Attribute("priority"), item.Priority);
                    if (itemElement.Attribute("authenticationType").Value != item.AuthenticationType.ToString() || itemElement.Attribute("accountName").Value != item.AccountName)
                    {
                        elementUpdates.Add(GetUpdateElement("authenticationType", item.AuthenticationType.ToString(), itemElement.Attribute("authenticationType").Value));
                        elementUpdates.Add(GetUpdateElement("accountName", item.AccountName, itemElement.Attribute("accountName").Value));
                    }
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (!content.CrawlRules.Exists(element.Attribute("path").Value))
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }
        #endregion

        #region Crawl Mappings
        /// <summary>
        /// Compare crawl mappings
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void CompareCrawlMappings(XElement search, XElement parent, Content content)
        {
            XElement elements = new XElement("crawlMappings");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement itemElement;
            XElement compare = search.Descendants("crawlMappings").First();
            foreach (CrawlMapping item in content.CrawlMappings)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(e => e.Attribute("source").Value == item.Source.ToString()).FirstOrDefault()) == null)
                    deletes.Add(new XElement("crawlMapping", new XAttribute("source", item.Source.ToString())));
                else if (itemElement.Attribute("target").Value != item.Target.ToString())
                {
                    deletes.Add(new XElement("crawlMapping", new XAttribute("source", item.Source.ToString())));
                    adds.Add(itemElement);
                }
            foreach (XElement element in compare.Nodes())
                if (!content.CrawlMappings.Exists(new Uri(element.Attribute("source").Value)))
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }
        #endregion

        #region File Types
        /// <summary>
        /// Compare file types
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void CompareFileTypes(XElement search, XElement parent, Content content)
        {
            XElement elements = new XElement("fileTypes");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement compare = search.Descendants("fileTypes").First();
            foreach (Extension item in content.ExtensionList)
                if (compare.Nodes().OfType<XElement>().Where(e => e.Attribute("extension").Value == item.FileExtension).FirstOrDefault() == null)
                    deletes.Add(new XElement("fileType", new XAttribute("extension", item.FileExtension)));
            foreach (XElement element in compare.Nodes())
                if (content.ExtensionList.Where(e => e.FileExtension == element.Attribute("extension").Value).FirstOrDefault() == null)
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }
        #endregion

        #region Crawler Impact Rules
        /// <summary>
        /// Compare crawler impact rules
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="service"></param>
        private void CompareCrawlerImpactRules(XElement search, XElement parent, SearchService service)
        {
            XElement elements = new XElement("crawlImpactRules");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement itemElement;
            XElement compare = search.Descendants("crawlImpactRules").First();
            foreach (SiteHitRule item in service.SiteHitRules)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(e => e.Attribute("site").Value == item.Site).FirstOrDefault()) == null)
                    deletes.Add(new XElement("crawlImpactRule", new XAttribute("site", item.Site)));
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("crawlImpactRule", new XAttribute("site", item.Site));
                    Compare(elementUpdates, itemElement.Attribute("behavior"), item.Behavior.ToString());
                    Compare(elementUpdates, itemElement.Attribute("hitRate"), item.HitRate);
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (!service.SiteHitRules.Exists(element.Attribute("site").Value))
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }
        #endregion

        #region Authoritative Pages
        /// <summary>
        /// Compare authoritative pages
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="ranking"></param>
        private void CompareAuthoritativePages(XElement search, XElement parent, Ranking ranking)
        {
            XElement elements = new XElement("authorityPages");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement itemElement;
            XElement compare = search.Descendants("authorityPages").First();
            foreach (AuthorityPage item in ranking.AuthorityPages)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(e => e.Attribute("url").Value == item.Url.ToString()).FirstOrDefault()) == null)
                    deletes.Add(new XElement("authorityPage", new XAttribute("url", item.Url.ToString())));
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("authorityPage", new XAttribute("url", item.Url.ToString()));
                    Compare(elementUpdates, itemElement.Attribute("level"), item.Level.ToString());
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (ranking.AuthorityPages.Where(e => e.Url.ToString() == element.Attribute("url").Value).FirstOrDefault() == null)
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Compare demoted sites
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="ranking"></param>
        private void CompareDemotedSites(XElement search, XElement parent, Ranking ranking)
        {
            XElement elements = new XElement("demotedSites");
            XElement adds = GetAddsElement();
            XElement deletes = GetDeletesElement();
            XElement compare = search.Descendants("demotedSites").First();
            foreach (DemotedSite item in ranking.DemotedSites)
                if (compare.Nodes().OfType<XElement>().Where(e => e.Attribute("url").Value == item.Url.ToString()).FirstOrDefault() == null)
                    deletes.Add(new XElement("demotedSite", new XAttribute("url", item.Url.ToString())));
            foreach (XElement element in compare.Nodes())
                if (ranking.DemotedSites.Where(e => e.Url.ToString() == element.Attribute("url").Value).FirstOrDefault() == null)
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }
        #endregion

        #region Query Suggestions
        /// <summary>
        /// Compare query suggestions
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void CompareQuerySuggestions(XElement search, XElement parent, SearchServiceApplication application)
        {
            XElement elements = new XElement("querySuggestions");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement compare = search.Descendants("querySuggestions").First();
            List<LanguageResourcePhrase> list = QuerySuggestionsExtension.GetAll(application);
            foreach (LanguageResourcePhrase item in list)
                if (compare.Nodes().OfType<XElement>().Where(element => QuerySuggestionsExtension.Equals(element, item)).FirstOrDefault() == null)
                    deletes.Add(new XElement("querySuggestion",
                        new XAttribute("culture", item.Culture.ToString()),
                        new XAttribute("type", item.Type.ToString()),
                        new XAttribute("phrase", item.Phrase),
                        new XAttribute("mapping", item.Mapping),
                        new XAttribute("tenantId", item.TenantId.ToString()),
                        new XAttribute("siteId", item.SiteId.ToString()),
                        new XAttribute("webId", item.WebId.ToString())));
            foreach (XElement element in compare.Nodes())
                if (list.Where(item => QuerySuggestionsExtension.Equals(element, item)).FirstOrDefault() == null)
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }
        #endregion

        #region Query Client Types
        /// <summary>
        /// Compare query client types
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void CompareQueryClientTypes(XElement search, XElement parent, SearchServiceApplication application)
        {
            XElement elements = new XElement("queryClientTypes");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement itemElement;
            XElement compare = search.Descendants("queryClientTypes").First();
            SearchObjectOwner owner = new SearchObjectOwner(SearchObjectLevel.Ssa);
            QueryClientTypeManager manager = new QueryClientTypeManager(application);
            List<QueryClientTypeMapping> mappings = manager.GetQueryClientTypeMappings(owner);
            foreach (QueryClientTypeMapping item in mappings)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(e => e.Attribute("name").Value == item.ClientType.ToString()).FirstOrDefault()) == null)
                    deletes.Add(new XElement("queryClientType", new XAttribute("name", item.ClientType)));
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("queryClientType", new XAttribute("name", item.ClientType));
                    Compare(elementUpdates, itemElement.Attribute("throttlingTier"), item.ThrottlingTier.ToString());
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (!mappings.Exists(item => element.Attribute("name").Value == item.ClientType.ToString()))
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }
        #endregion

        #region Result Sources
        /// <summary>
        /// Compare result sources
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        /// <param name="manager"></param>
        private void CompareResultSources(XElement search, XElement parent, SearchServiceApplication application, FederationManager manager)
        {
            CompareResultSources(search, parent, ResultSourcesExtension.GetOwners(application), manager);
            CompareDefaultResultSources(search, parent, ResultSourcesExtension.GetDefaultResultSourceOwners(application), manager);
        }

        /// <summary>
        /// Compare result sources
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        protected void CompareResultSources(XElement search, XElement parent, List<SearchObjectOwner> owners, FederationManager manager)
        {
            XElement elements = new XElement("resultSources");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement compare = search.Descendants("resultSources").First();
            XElement itemElement;
            List<Source> list = new List<Source>();
            foreach (SearchObjectOwner owner in owners)
                list.AddRange(manager.ListSources(new SearchObjectFilter(owner) { IncludeHigherLevel = false, IncludeLowerLevels = false }, true));
            foreach (Source item in list)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(element => ResultSourcesExtension.Equals(element, item, item.Owner)).FirstOrDefault()) == null)
                    deletes.Add(new XElement("resultSource",
                        new XAttribute("name", item.Name),
                        new XAttribute("level", item.Owner.Level.ToString()),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString())));
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("resultSource",
                        new XAttribute("name", item.Name),
                        new XAttribute("level", item.Owner.Level.ToString()),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString()));
                    Compare(elementUpdates, itemElement.Attribute("description"), item.Description);
                    Compare(elementUpdates, itemElement.Attribute("active"), item.Active);
                    Compare(elementUpdates, itemElement.Attribute("providerId"), item.ProviderId.ToString());
                    Compare(elementUpdates, itemElement.Attribute("url"), item.ConnectionUrlTemplate ?? string.Empty);
                    string authenticationType = item.HasPermissionToReadAuthInfo ? item.AuthInfo.AuthenticationType.ToString() : string.Empty;
                    string accountName = item.HasPermissionToReadAuthInfo ? ResultSourcesExtension.GetResultSourceAccountName(item.AuthInfo.Data) : string.Empty;
                    if (itemElement.Attribute("authenticationType").Value != authenticationType || itemElement.Attribute("accountName").Value != accountName)
                    {
                        elementUpdates.Add(GetUpdateElement("authenticationType", authenticationType, itemElement.Attribute("authenticationType").Value));
                        elementUpdates.Add(GetUpdateElement("accountName", accountName, itemElement.Attribute("accountName").Value));
                    }
                    CompareResultSourceQueryTransform(elementUpdates, itemElement, item.QueryTransform, manager, item.Owner);
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (list.Where(item => ResultSourcesExtension.Equals(element, item, item.Owner)).FirstOrDefault() == null)
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Compare default result sources
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        /// <param name="manager"></param>
        protected void CompareDefaultResultSources(XElement search, XElement parent, List<SearchObjectOwner> owners, FederationManager manager)
        {
            XElement elements = new XElement("defaultResultSources");
            XElement updates = GetUpdatesElement();
            XElement compare = search.Descendants("defaultResultSources").First();
            List<Source> list = new List<Source>();
            foreach (SearchObjectOwner owner in owners)
                list.Add(manager.GetDefaultSource(owner));
            foreach (XElement element in compare.Nodes())
                if (list.Where(item => ResultSourcesExtension.Equals(element, item, SearchObjectOwnerExtension.GetOwner(element, owners))).FirstOrDefault() == null)
                    updates.Add(new XElement("defaultResultSource",
                        new XAttribute("name", element.Attribute("name").Value),
                        new XAttribute("level", element.Attribute("level").Value),
                        new XAttribute("siteId", element.Attribute("siteId").Value),
                        new XAttribute("webId", element.Attribute("webId").Value)));
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Compare result source query transform
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="itemElement"></param>
        /// <param name="queryTransform"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        private void CompareResultSourceQueryTransform(XElement updates, XElement itemElement, QueryTransform queryTransform, FederationManager manager, SearchObjectOwner owner)
        {
            XElement queryTransformElement = itemElement.Descendants("queryTransform").FirstOrDefault();
            AddUpdateElement(updates, queryTransformElement, GetQueryTransformText(queryTransform, manager, owner), GetQueryTransformText(queryTransformElement));
        }

        /// <summary>
        /// Return query transform text
        /// </summary>
        /// <param name="queryTransform"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        /// <returns></returns>
        private string GetQueryTransformText(QueryTransform queryTransform, FederationManager manager, SearchObjectOwner owner)
        {
            StringBuilder text = new StringBuilder();
            if (queryTransform != null)
            {
                text.AppendFormat("queryTemplate={0}", queryTransform.QueryTemplate);
                if (queryTransform.SourceId != null && queryTransform.SourceId.Value != Guid.Empty)
                {
                    Source item = manager.GetSource(queryTransform.SourceId.Value, owner);
                    text.AppendFormat(",source=(name={0},level={1},siteId={2},webId={3})", item.Name, owner.Level, owner.SPSiteId.ToString(), owner.SPWebId.ToString());
                }
                text.Append(",overrideProperties=(");
                if (queryTransform.OverrideProperties != null)
                    foreach (KeyValuePair<string, object> item in queryTransform.OverrideProperties)
                        text.AppendFormat("{0}={1}:{2},", item.Key, item.Value.GetType().Name, GetQueryTransformValue(item.Value));
                text.Append("),queryTemplateParameters=(");
                if (queryTransform.QueryTemplateParameters != null)
                    foreach (KeyValuePair<string, bool> item in queryTransform.QueryTemplateParameters)
                        text.AppendFormat("{0}={1},", item.Key, item.Value.ToString().ToLower());
                text.Append(");");
            }
            return text.ToString();
        }

        /// <summary>
        /// Return query transform text
        /// </summary>
        /// <param name="queryTransform"></param>
        /// <returns></returns>
        private string GetQueryTransformText(XElement queryTransform)
        {
            StringBuilder text = new StringBuilder();
            if (queryTransform != null)
            {
                XElement overridePropertiesElement, queryTemplateParametersElement;
                text.AppendFormat("queryTemplate={0}", queryTransform.Attribute("queryTemplate") != null ? queryTransform.Attribute("queryTemplate").Value : string.Empty);
                XElement item;
                if ((item = queryTransform.Descendants("source").FirstOrDefault()) != null)
                    text.AppendFormat(",source=(name={0},level={1},siteId={2},webId={3})", item.Attribute("name").Value, item.Attribute("level").Value, item.Attribute("siteId").Value, item.Attribute("webId").Value);
                text.Append(",overrideProperties=(");
                if ((overridePropertiesElement = queryTransform.Descendants("overrideProperties").FirstOrDefault()) != null)
                    foreach (XElement element in overridePropertiesElement.Nodes())
                        text.AppendFormat("{0}={1}:{2},", element.Attribute("key").Value, element.Attribute("type").Value, GetQueryTransformElementValue(element));
                text.Append("),queryTemplateParameters=(");
                if ((queryTemplateParametersElement = queryTransform.Descendants("queryTemplateParameters").FirstOrDefault()) != null)
                    foreach (XElement element in queryTemplateParametersElement.Nodes())
                        text.AppendFormat("{0}={1},", element.Attribute("key").Value, element.Attribute("value").Value);
                text.Append(");");
            }
            return text.ToString();
        }

        /// <summary>
        /// Return query transform value
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private string GetQueryTransformValue(object value)
        {
            if (value is int)
                return value.ToString();
            else if (value is string)
                return (string)value;
            else if (value is StringCollection)
            {
                StringBuilder result = new StringBuilder();
                foreach (string item in (StringCollection)value)
                    result.Append(item).Append(",");
                return result.ToString();
            }
            else if (value is SortCollection)
            {
                StringBuilder result = new StringBuilder();
                foreach (Sort item in (SortCollection)value)
                    result.AppendFormat("{0}-{1},", item.Direction, item.Property);
                return result.ToString();
            }
            else if (value is ReorderingRuleCollection)
            {
                StringBuilder result = new StringBuilder();
                foreach (ReorderingRule item in (ReorderingRuleCollection)value)
                    result.AppendFormat("{0}-{1}-{2},", item.Boost, item.MatchType.ToString(), item.MatchValue);
                return result.ToString();
            }
            throw new Exception(string.Format("Query transform value type '{0}' is not supported.", value.GetType().Name));
        }

        /// <summary>
        /// Return query transform element value
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private string GetQueryTransformElementValue(XElement element)
        {
            string type = element.Attribute("type").Value;
            if (type == "Int32" || type == "String")
                return element.Attribute("value").Value;
            else if (type == "StringCollection")
            {
                StringBuilder result = new StringBuilder();
                foreach (XElement item in element.Descendants("value"))
                    result.Append(item.Value).Append(",");
                return result.ToString();
            }
            else if (type == "SortCollection")
            {
                StringBuilder result = new StringBuilder();
                foreach (XElement item in element.Descendants("value"))
                    result.AppendFormat("{0}-{1},", item.Attribute("direction").Value, item.Attribute("property").Value);
                return result.ToString();
            }
            else if (type == "ReorderingRuleCollection")
            {
                StringBuilder result = new StringBuilder();
                foreach (XElement item in element.Descendants("value"))
                    result.AppendFormat("{0}-{1}-{2},", item.Attribute("boost").Value, item.Attribute("matchType").Value, item.Attribute("matchValue").Value);
                return result.ToString();
            }
            throw new Exception(string.Format("Query transform value type '{0}' is not supported.", type));
        }
        #endregion

        #region Result Types
        /// <summary>
        /// Compare result types
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="federationManager"></param>
        /// <param name="resultItemTypeManager"></param>
        protected void CompareResultTypes(XElement search, XElement parent, List<SearchObjectOwner> owners, FederationManager federationManager, ResultItemTypeManager resultItemTypeManager)
        {
            XElement elements = new XElement("resultTypes");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement compare = search.Descendants("resultTypes").First();
            XElement itemElement;
            List<ResultItemType> list = new List<ResultItemType>();
            foreach (SearchObjectOwner owner in owners)
                list.AddRange(resultItemTypeManager.GetResultItemTypes(owner, false));
            foreach (ResultItemType item in list)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(element => ResultTypesExtension.Equals(element, item, item.Owner)).FirstOrDefault()) == null)
                    deletes.Add(new XElement("resultType",
                        new XAttribute("name", item.Name),
                        new XAttribute("level", item.Owner.Level.ToString()),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString())));
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("resultType",
                        new XAttribute("name", item.Name),
                        new XAttribute("level", item.Owner.Level.ToString()),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString()));
                    Compare(elementUpdates, itemElement.Attribute("optimize"), item.OptimizeForFrequentUse);
                    Compare(elementUpdates, itemElement.Attribute("priority"), item.RulePriority);
                    Compare(elementUpdates, itemElement.Attribute("displayTemplateUrl"), item.DisplayTemplateUrl);
                    Compare(elementUpdates, itemElement.Attribute("displayProperties"), item.DisplayProperties);
                    CompareResultTypeSource(elementUpdates, itemElement.Descendants("resultSource").FirstOrDefault(), item.SourceID, federationManager, item.Owner);
                    CompareResultTypePropertyRules(elementUpdates, itemElement.Descendants("rules").FirstOrDefault(), item.Rules);
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (list.Where(item => ResultTypesExtension.Equals(element, item, item.Owner)).FirstOrDefault() == null)
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Compare result type source
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="sourceIdElement"></param>
        /// <param name="sourceId"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        private void CompareResultTypeSource(XElement updates, XElement sourceIdElement, Guid sourceId, FederationManager manager, SearchObjectOwner owner)
        {
            string elementText = string.Empty;
            if (sourceIdElement != null)
                elementText = string.Format("{0},{1},{2},{3}",
                    sourceIdElement.Attribute("name").Value,
                    sourceIdElement.Attribute("level").Value,
                    sourceIdElement.Attribute("siteId").Value,
                    sourceIdElement.Attribute("webId").Value);
            string text = string.Empty;
            if (sourceId != null && sourceId != Guid.Empty)
            {
                Source item = manager.GetSource(sourceId, owner);
                text = string.Format("{0},{1},{2},{3}",
                    item.Name,
                    item.Owner.Level,
                    item.Owner.SPSiteId.ToString(),
                    item.Owner.SPWebId.ToString());
            }
            if (text != elementText)
            {
                XElement update = GetUpdateElement(sourceIdElement.Name, text, elementText);
                update.Add(sourceIdElement);
                updates.Add(update);
            }
        }

        /// <summary>
        /// Compare result type property rules
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="contextConditionsElement"></param>
        /// <param name="contextConditions"></param>
        private void CompareResultTypePropertyRules(XElement updates, XElement propertyRulesElement, PropertyRuleCollection propertyRules)
        {
            StringBuilder elementText = new StringBuilder();
            if (propertyRulesElement != null)
                foreach (XElement element in propertyRulesElement.Nodes())
                {
                    elementText.AppendFormat("{0}{1}", element.Attribute("name").Value, element.Attribute("operator").Value);
                    foreach (XElement value in element.Descendants("value"))
                        elementText.AppendFormat("{0},", value.Value);
                    elementText.Append(";");
                }
            StringBuilder text = new StringBuilder();
            if (propertyRules != null)
                foreach (PropertyRule item in propertyRules.PropertyRules)
                {
                    text.AppendFormat("{0}{1}", item.PropertyName, item.PropertyOperator.Representation);
                    foreach (string value in item.PropertyValues)
                        text.AppendFormat("{0},", value);
                    text.Append(";");
                }
            AddUpdateElement(updates, propertyRulesElement, text, elementText);
        }
        #endregion

        #region Query Rules
        /// <summary>
        /// Compare query rules
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="federationManager"></param>
        /// <param name="queryRuleManager"></param>
        protected void CompareQueryRules(XElement search, XElement parent, List<SearchObjectOwner> owners, FederationManager federationManager, QueryRuleManager queryRuleManager)
        {
            XElement elements = new XElement("queryRules");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement compare = search.Descendants("queryRules").First();
            XElement itemElement;
            List<QueryRule> list = new List<QueryRule>();
            List<QueryRuleGroup> groups = new List<QueryRuleGroup>();
            List<UserSegment> segments = new List<UserSegment>();
            List<Microsoft.Office.Server.Search.Query.Rules.BestBet> bestBets = new List<Microsoft.Office.Server.Search.Query.Rules.BestBet>();
            foreach (SearchObjectOwner owner in owners)
            {
                SearchObjectFilter filter = new SearchObjectFilter(owner) { IncludeHigherLevel = false, IncludeLowerLevels = false };
                groups.AddRange(queryRuleManager.GetQueryRuleGroups(filter));
                segments.AddRange(queryRuleManager.GetUserSegments(filter));
                bestBets.AddRange(queryRuleManager.GetBestBets(filter));
                list.AddRange(queryRuleManager.GetQueryRules(filter));
            }
            foreach (QueryRule item in list)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(element => QueryRulesExtension.Equals(element, item, item.Owner)).FirstOrDefault()) == null)
                    deletes.Add(new XElement("queryRule",
                        new XAttribute("name", item.DisplayName),
                        new XAttribute("level", item.Owner.Level.ToString()),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString())));
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("queryRule",
                        new XAttribute("name", item.DisplayName),
                        new XAttribute("level", item.Owner.Level.ToString()),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString()));
                    Compare(elementUpdates, itemElement.Attribute("active"), item.IsActive);
                    Compare(elementUpdates, itemElement.Attribute("startDate"), Tools.GetDateValue(item.StartDate));
                    Compare(elementUpdates, itemElement.Attribute("endDate"), Tools.GetDateValue(item.EndDate));
                    Compare(elementUpdates, itemElement.Attribute("reviewDate"), Tools.GetDateValue(item.ReviewDate));
                    Compare(elementUpdates, itemElement.Attribute("contact"), item.Contact);
                    Compare(elementUpdates, itemElement.Attribute("group"), item.GroupId != null ? groups.Where(i => i.Id == item.GroupId.Value).First().Name : string.Empty);
                    Compare(elementUpdates, itemElement.Attribute("processing"), item.ProcessingDirective.ToString());
                    CompareQueryRuleContextConditions(elementUpdates, itemElement.Descendants("contextConditions").FirstOrDefault(), item.ContextConditions, federationManager, item.Owner, segments);
                    CompareQueryRuleQueryConditions(elementUpdates, itemElement.Descendants("queryConditions").FirstOrDefault(), item.QueryConditions);
                    CompareQueryRuleBestBetsAction(elementUpdates, itemElement.Descendants("bestBetActions").FirstOrDefault(), item.AssignBestBetsAction, bestBets);
                    CompareQueryRuleResultBlockActions(elementUpdates, itemElement.Descendants("queryActions").FirstOrDefault(), item.CreateResultBlockActions, federationManager, item.Owner);
                    CompareQueryRuleChangeQueryAction(elementUpdates, itemElement.Descendants("changeQueryAction").FirstOrDefault(), item.ChangeQueryAction, federationManager, item.Owner);
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (list.Where(item => QueryRulesExtension.Equals(element, item, item.Owner)).FirstOrDefault() == null)
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Compare query rule context conditions
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="contextConditionsElement"></param>
        /// <param name="contextConditions"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        /// <param name="segments"></param>
        private void CompareQueryRuleContextConditions(XElement updates, XElement contextConditionsElement, ContextConditionCollection contextConditions, FederationManager manager, SearchObjectOwner owner, List<UserSegment> segments)
        {
            StringBuilder elementText = new StringBuilder();
            if (contextConditionsElement != null)
                foreach (XElement element in contextConditionsElement.Nodes())
                    if (element.Attribute("type").Value == "Source")
                        elementText.AppendFormat("type=Source,name={0},level={1},siteId={2},webId={3};", element.Attribute("name").Value, element.Attribute("level").Value, element.Attribute("siteId").Value, element.Attribute("webId").Value);
                    else if (element.Attribute("type").Value == "Category")
                        elementText.AppendFormat("type=Category,termStoreId={0},termSetId={1},termId={2};", element.Attribute("termStoreId").Value, element.Attribute("termSetId").Value, element.Attribute("termId").Value);
                    else if (element.Attribute("type").Value == "UserSegment")
                    {
                        elementText.AppendFormat("type=UserSegment,name={0},level={1},siteId={2},webId={3},terms=", element.Attribute("name").Value, element.Attribute("level").Value, element.Attribute("siteId").Value, element.Attribute("webId").Value);
                        foreach (XElement value in element.Descendants("term"))
                            elementText.AppendFormat("{0},", value.Value);
                        elementText.Append(";");
                    }
                    else if (element.Attribute("type").Value == "Generic")
                        elementText.AppendFormat("type=Generic,expression={0};", element.Attribute("expression").Value);
                    else
                        throw new Exception(string.Format("Query rule context condition type '{0}' is not supported.", element.Attribute("type").Value));
            StringBuilder text = new StringBuilder();
            if (contextConditions != null)
                foreach (ContextCondition item in contextConditions)
                    if (item is SourceContextCondition)
                    {
                        Source source = manager.GetSource(((SourceContextCondition)item).SourceId, owner);
                        text.AppendFormat("type=Source,name={0},level={1},siteId={2},webId={3};", source.Name, source.Owner.Level, source.Owner.SPSiteId.ToString(), source.Owner.SPWebId.ToString());
                    }
                    else if (item is CategoryContextCondition)
                        text.AppendFormat("type=Category,termStoreId={0},termSetId={1},termId={2};", ((CategoryContextCondition)item).TermStoreId, ((CategoryContextCondition)item).TermSetId, ((CategoryContextCondition)item).TermId);
                    else if (item is UserSegmentContextCondition)
                    {
                        UserSegment segment = segments.Where(i => i.Id == ((UserSegmentContextCondition)item).UserSegmentId).First();
                        text.AppendFormat("type=UserSegment,name={0},level={1},siteId={2},webId={3},terms=", segment.DisplayName, segment.Owner.Level, segment.Owner.SPSiteId.ToString(), segment.Owner.SPWebId.ToString());
                        foreach (Guid value in segment.Terms)
                            text.AppendFormat("{0},", value.ToString());
                        text.Append(";");
                    }
                    else if (item is GenericContextCondition)
                        text.AppendFormat("type=Generic,expression={0};", ((GenericContextCondition)item).Expression);
                    else
                        throw new Exception(string.Format("Query rule context condition type '{0}' is not supported.", item.GetType().Name));
            AddUpdateElement(updates, contextConditionsElement, text, elementText);
        }

        /// <summary>
        /// Compare query rule query conditions
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="queryConditionsElement"></param>
        /// <param name="queryConditions"></param>
        private void CompareQueryRuleQueryConditions(XElement updates, XElement queryConditionsElement, QueryConditionCollection queryConditions)
        {
            StringBuilder elementText = new StringBuilder();
            if (queryConditionsElement != null)
                foreach (XElement element in queryConditionsElement.Nodes())
                {
                    elementText.AppendFormat("match={0},terms={1},boundVariables=(", element.Attribute("match").Value, element.Attribute("terms").Value);
                    foreach (XElement value in element.Descendants("boundVariable"))
                        elementText.AppendFormat("name={0},origin={1},type={2};", value.Attribute("name").Value, value.Attribute("origin").Value, value.Attribute("type").Value);
                    elementText.Append(")");
                    if (element.Attribute("type").Value == "Keyword")
                    {
                        elementText.Append(",type=Keyword,terms=");
                        foreach (XElement value in element.Descendants("term"))
                            elementText.AppendFormat("{0},", value.Value);
                    }
                    else if (element.Attribute("type").Value == "PeopleName")
                        elementText.Append(",type=PeopleName");
                    else if (element.Attribute("type").Value == "CommonQuery")
                    {
                        elementText.AppendFormat(",type=CommonQuery,source={0},threshold={1},additions=", element.Attribute("source").Value, element.Attribute("threshold").Value);
                        foreach (XElement value in element.Descendants("addition"))
                            elementText.AppendFormat("{0},", value.Value);
                    }
                    else if (element.Attribute("type").Value == "CommonlyClickedProperty")
                    {
                        elementText.AppendFormat(",type=CommonlyClickedProperty,property={0},threshold={1},values=", element.Attribute("property").Value, element.Attribute("threshold").Value);
                        foreach (XElement value in element.Descendants("value"))
                            elementText.AppendFormat("{0},", value.Value);
                    }
                    else if (element.Attribute("type").Value == "RegularExpression")
                        elementText.AppendFormat(",type=RegularExpression,pattern={0}", element.Attribute("pattern").Value);
                    else if (element.Attribute("type").Value == "TaxonomyDictionary")
                        elementText.AppendFormat(",type=TaxonomyDictionary,name={0},termStoreId={1},termSetId={2},termId={3}", element.Attribute("name").Value, element.Attribute("termStoreId").Value, element.Attribute("termSetId").Value, element.Attribute("termId").Value);
                    else
                        throw new Exception(string.Format("Query rule condition type '{0}' is not supported.", element.Attribute("type").Value));
                    elementText.Append(";");
                }
            StringBuilder text = new StringBuilder();
            if (queryConditions != null)
                foreach (QueryCondition item in queryConditions)
                {
                    text.AppendFormat("match={0},terms={1},boundVariables=(", item.MatchingOptions, item.SubjectTermsOrigin);
                    foreach (BoundVariableDefinition variable in item.BoundVariables)
                        text.AppendFormat("name={0},origin={1},type={2};", variable.Name, variable.Origin, variable.Type);
                    text.Append(")");
                    if (item is KeywordCondition)
                    {
                        text.Append(",type=Keyword,terms=");
                        foreach (string term in ((KeywordCondition)item).Terms)
                            text.AppendFormat("{0},", term);
                    }
                    else if (item is PeopleNameCondition)
                        text.Append(",type=PeopleName");
                    else if (item is CommonQueryCondition)
                    {
                        text.AppendFormat(",type=CommonQuery,source={0},threshold={1},additions=", ((CommonQueryCondition)item).SourceName, ((CommonQueryCondition)item).Threshold);
                        foreach (string addition in ((CommonQueryCondition)item).Additions)
                            text.AppendFormat("{0},", addition);
                    }
                    else if (item is CommonlyClickedPropertyCondition)
                    {
                        text.AppendFormat(",type=CommonlyClickedProperty,property={0},threshold={1},values=", ((CommonlyClickedPropertyCondition)item).PropertyName, ((CommonlyClickedPropertyCondition)item).Threshold);
                        foreach (string value in ((CommonlyClickedPropertyCondition)item).PropertyValues)
                            text.AppendFormat("{0},", value);
                    }
                    else if (item is RegularExpressionCondition)
                        text.AppendFormat(",type=RegularExpression,pattern={0}", ((RegularExpressionCondition)item).Pattern);
                    else if (item is TaxonomyDictionaryCondition)
                        text.AppendFormat(",type=TaxonomyDictionary,name={0},termStoreId={1},termSetId={2},termId={3}", ((TaxonomyDictionaryCondition)item).DisplayName, ((TaxonomyDictionaryCondition)item).TermStoreId, ((TaxonomyDictionaryCondition)item).TermSetId, ((TaxonomyDictionaryCondition)item).TermId);
                    else
                        throw new Exception(string.Format("Query rule condition type '{0}' is not supported.", item.GetType().Name));
                    text.Append(";");
                }
            AddUpdateElement(updates, queryConditionsElement, text, elementText);
        }

        /// <summary>
        /// Compare query rule best bets action
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="bestBetsActionElement"></param>
        /// <param name="bestBetsAction"></param>
        /// <param name="bestBets"></param>
        private void CompareQueryRuleBestBetsAction(XElement updates, XElement bestBetsActionElement, AssignBestBetsAction bestBetsAction, List<Microsoft.Office.Server.Search.Query.Rules.BestBet> bestBets)
        {
            StringBuilder elementText = new StringBuilder();
            if (bestBetsActionElement != null)
                foreach (XElement element in bestBetsActionElement.Nodes())
                    elementText.AppendFormat("title={0},url={1},visualBestBet={2},description={3};", element.Attribute("title").Value, element.Attribute("url").Value, element.Attribute("visualBestBet").Value, element.Attribute("description").Value);
            StringBuilder text = new StringBuilder();
            if (bestBetsAction != null)
                foreach (Guid item in bestBetsAction.BestBetIds)
                {
                    Microsoft.Office.Server.Search.Query.Rules.BestBet bestBet = bestBets.Where(i => i.Id == item).First();
                    text.AppendFormat("title={0},url={1},visualBestBet={2},description={3};", bestBet.Title, bestBet.Url, bestBet.IsVisualBestBet.ToString().ToLower(), bestBet.Description);
                }
            AddUpdateElement(updates, bestBetsActionElement, text, elementText);
        }

        /// <summary>
        /// Compare query rule result block actions
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="queryActionsElement"></param>
        /// <param name="queryActions"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        private void CompareQueryRuleResultBlockActions(XElement updates, XElement queryActionsElement, QueryActionCollection<CreateResultBlockAction> queryActions, FederationManager manager, SearchObjectOwner owner)
        {
            StringBuilder elementText = new StringBuilder();
            if (queryActionsElement != null)
                foreach (XElement element in queryActionsElement.Nodes())
                    elementText.AppendFormat("title={0},alwaysShow={1},resultTitleUrl={2},groupTemplate={3},itemTemplate={4},queryTransform=({5});", element.Attribute("title").Value, element.Attribute("alwaysShow").Value, element.Attribute("resultTitleUrl").Value, element.Attribute("groupTemplate").Value, element.Attribute("itemTemplate").Value, GetQueryTransformText(element.Descendants("queryTransform").FirstOrDefault()));
            StringBuilder text = new StringBuilder();
            if (queryActions != null)
                foreach (CreateResultBlockAction item in queryActions)
                    text.AppendFormat("title={0},alwaysShow={1},resultTitleUrl={2},groupTemplate={3},itemTemplate={4},queryTransform=({5});", item.ResultTitle.DefaultLanguageString, item.AlwaysShow.ToString().ToLower(), item.ResultTitleUrl, item.GroupTemplateId, item.ItemTemplateId, GetQueryTransformText(item.QueryTransform, manager, owner));
            AddUpdateElement(updates, queryActionsElement, text, elementText);
        }

        /// <summary>
        /// Compare query rule change query action
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="changeQueryActionElement"></param>
        /// <param name="changeQueryAction"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        private void CompareQueryRuleChangeQueryAction(XElement updates, XElement changeQueryActionElement, ChangeQueryAction changeQueryAction, FederationManager manager, SearchObjectOwner owner)
        {
            string elementText = changeQueryActionElement != null ?
                string.Format("queryTransform=({0});", GetQueryTransformText(changeQueryActionElement.Descendants("queryTransform").FirstOrDefault())) : string.Empty;
            string text = changeQueryAction != null ?
                string.Format("queryTransform=({0});", GetQueryTransformText(changeQueryAction.QueryTransform, manager, owner)) : string.Empty;
            AddUpdateElement(updates, changeQueryActionElement, text, elementText);
        }
        #endregion

        #region Managed Properties
        /// <summary>
        /// Compare managed properties
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void CompareManagedProperties(XElement search, XElement parent, SearchServiceApplication application)
        {
            Dictionary<int, SearchObjectOwner> owners = new Dictionary<int, SearchObjectOwner>();
            List<ManagedPropertyInfo> items = new List<ManagedPropertyInfo>();
            foreach (SearchObjectOwner owner in ManagedPropertiesExtension.GetOwners(application))
                foreach (ManagedPropertyInfo item in application.GetManagedProperties(null, int.MaxValue, owner))
                    if (!owners.ContainsKey(item.Pid))
                    {
                        items.Add(item);
                        owners.Add(item.Pid, owner);
                    }
            CompareManagedProperties(search, parent, items, owners);
        }

        /// <summary>
        /// Compare managed properties
        /// </summary>
        /// <param name="search"></param>
        /// <param name="parent"></param>
        /// <param name="items"></param>
        /// <param name="owners"></param>
        protected void CompareManagedProperties(XElement search, XElement parent, List<ManagedPropertyInfo> items, Dictionary<int, SearchObjectOwner> owners)
        {
            XElement elements = new XElement("managedProperties");
            XElement adds = GetAddsElement();
            XElement updates = GetUpdatesElement();
            XElement deletes = GetDeletesElement();
            XElement compare = search.Descendants("managedProperties").First();
            XElement itemElement;
            foreach (ManagedPropertyInfo item in items)
                if ((itemElement = compare.Nodes().OfType<XElement>().Where(element => ManagedPropertiesExtension.Equals(element, item, owners[item.Pid])).FirstOrDefault()) == null)
                    deletes.Add(new XElement("managedProperty",
                        new XAttribute("name", item.Name),
                        new XAttribute("level", owners[item.Pid].Level.ToString()),
                        new XAttribute("siteId", owners[item.Pid].SPSiteId.ToString())));
                else
                {
                    XElement elementUpdates = GetUpdatesElement();
                    XElement element = new XElement("managedProperty",
                        new XAttribute("name", item.Name),
                        new XAttribute("level", owners[item.Pid].Level.ToString()),
                        new XAttribute("siteId", owners[item.Pid].SPSiteId.ToString()));
                    Compare(elementUpdates, itemElement.Attribute("description"), item.Description);
                    Compare(elementUpdates, itemElement.Attribute("type"), item.ManagedType.ToString());
                    Compare(elementUpdates, itemElement.Attribute("searchable"), item.Searchable);
                    Compare(elementUpdates, itemElement.Attribute("queryable"), item.Queryable);
                    Compare(elementUpdates, itemElement.Attribute("retrievable"), item.Retrievable);
                    Compare(elementUpdates, itemElement.Attribute("multipleValues"), item.HasMultipleValues);
                    Compare(elementUpdates, itemElement.Attribute("refinable"), item.Refinable);
                    Compare(elementUpdates, itemElement.Attribute("sortable"), item.Sortable);
                    Compare(elementUpdates, itemElement.Attribute("sortableType"), item.SortableType.ToString());
                    Compare(elementUpdates, itemElement.Attribute("safeForAnonymous"), item.SafeForAnonymous);
                    Compare(elementUpdates, itemElement.Attribute("tokenNormalization"), item.TokenNormalization);
                    Compare(elementUpdates, itemElement.Attribute("completeMatching"), item.CompleteMatching);
                    Compare(elementUpdates, itemElement.Attribute("companyExtraction"), item.CompanyExtraction);
                    Compare(elementUpdates, itemElement.Attribute("wordExactExtractionCustom"), item.WordExactExtractionCustom);
                    Compare(elementUpdates, itemElement.Attribute("wordExtractionCustom1"), item.WordExtractionCustom1);
                    Compare(elementUpdates, itemElement.Attribute("wordExtractionCustom2"), item.WordExtractionCustom2);
                    Compare(elementUpdates, itemElement.Attribute("wordExtractionCustom3"), item.WordExtractionCustom3);
                    Compare(elementUpdates, itemElement.Attribute("wordExtractionCustom4"), item.WordExtractionCustom4);
                    Compare(elementUpdates, itemElement.Attribute("wordExtractionCustom5"), item.WordExtractionCustom5);
                    Compare(elementUpdates, itemElement.Attribute("wordPartExactExtractionCustom"), item.WordPartExactExtractionCustom);
                    Compare(elementUpdates, itemElement.Attribute("wordPartExtractionCustom1"), item.WordPartExtractionCustom1);
                    Compare(elementUpdates, itemElement.Attribute("wordPartExtractionCustom2"), item.WordPartExtractionCustom2);
                    Compare(elementUpdates, itemElement.Attribute("wordPartExtractionCustom3"), item.WordPartExtractionCustom3);
                    Compare(elementUpdates, itemElement.Attribute("wordPartExtractionCustom4"), item.WordPartExtractionCustom4);
                    Compare(elementUpdates, itemElement.Attribute("wordPartExtractionCustom5"), item.WordPartExtractionCustom5);
                    Compare(elementUpdates, itemElement.Attribute("context"), item.Context);
                    Compare(elementUpdates, itemElement.Attribute("deleteDisallowed"), item.DeleteDisallowed);
                    Compare(elementUpdates, itemElement.Attribute("enabledForScoping"), item.EnabledForScoping);
                    Compare(elementUpdates, itemElement.Attribute("entityExtractorBitMap"), item.EntityExtractorBitMap);
                    Compare(elementUpdates, itemElement.Attribute("fullTextIndex"), item.FullTextIndex ?? string.Empty);
                    Compare(elementUpdates, itemElement.Attribute("indexOptions"), item.IndexOptions);
                    Compare(elementUpdates, itemElement.Attribute("mappingDisallowed"), item.MappingDisallowed);
                    Compare(elementUpdates, itemElement.Attribute("removeDuplicates"), item.RemoveDuplicates);
                    Compare(elementUpdates, itemElement.Attribute("respectPriority"), item.RespectPriority);
                    Compare(elementUpdates, itemElement.Attribute("updateGroup"), item.UpdateGroup ?? string.Empty);
                    CompareManagedPropertyRefinerConfiguration(elementUpdates, itemElement.Descendants("refinerConfiguration").FirstOrDefault(), item.RefinerConfiguration);
                    CompareManagedPropertyAliases(elementUpdates, itemElement.Descendants("aliases").FirstOrDefault(), item.Aliases);
                    CompareManagedPropertyMappedCrawledProperties(elementUpdates, itemElement.Descendants("crawledProperties").FirstOrDefault(), item.MappedCrawledProperties);
                    Tools.RollupElement(element, elementUpdates);
                    Tools.RollupElement(updates, element);
                }
            foreach (XElement element in compare.Nodes())
                if (items.Where(item => ManagedPropertiesExtension.Equals(element, item, owners[item.Pid])).FirstOrDefault() == null)
                    adds.Add(element);
            Tools.RollupElement(elements, adds);
            Tools.RollupElement(elements, updates);
            Tools.RollupElement(elements, deletes);
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Compare managed property refiner configuration
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="refinerConfigurationElement"></param>
        /// <param name="refinerConfiguration"></param>
        private void CompareManagedPropertyRefinerConfiguration(XElement updates, XElement refinerConfigurationElement, RefinerConfiguration refinerConfiguration)
        {
            string elementText = refinerConfigurationElement != null ?
                string.Format("type={0},anchoring={1},cutoffMaxBuckets={2},divisor={3},intervals={4},resolution={5}",
                refinerConfigurationElement.Attribute("type").Value,
                refinerConfigurationElement.Attribute("anchoring").Value,
                refinerConfigurationElement.Attribute("cutoffMaxBuckets").Value,
                refinerConfigurationElement.Attribute("divisor").Value,
                refinerConfigurationElement.Attribute("intervals").Value,
                refinerConfigurationElement.Attribute("resolution").Value) : string.Empty;
            string text = refinerConfiguration != null ?
                string.Format("type={0},anchoring={1},cutoffMaxBuckets={2},divisor={3},intervals={4},resolution={5}",
                refinerConfiguration.Type,
                refinerConfiguration.Anchoring,
                refinerConfiguration.CutoffMaxBuckets,
                refinerConfiguration.Divisor,
                refinerConfiguration.Intervals,
                refinerConfiguration.Resolution) : string.Empty;
            AddUpdateElement(updates, refinerConfigurationElement, text, elementText);
        }

        /// <summary>
        /// Compare managed property aliases
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="aliasesElement"></param>
        /// <param name="aliases"></param>
        private void CompareManagedPropertyAliases(XElement updates, XElement aliasesElement, List<string> aliases)
        {
            StringBuilder elementText = new StringBuilder();
            if (aliasesElement != null)
                foreach (XElement element in aliasesElement.Nodes())
                    elementText.AppendFormat("{0};", element.Value);
            StringBuilder text = new StringBuilder();
            if (aliases != null)
                foreach (string item in aliases)
                    text.AppendFormat("{0};", item);
            if (aliasesElement == null && aliases != null && aliases.Count > 0)
                aliasesElement = new XElement("aliases");
            AddUpdateElement(updates, aliasesElement, text, elementText);
        }

        /// <summary>
        /// Compare managed property mapped crawled properties
        /// </summary>
        /// <param name="updates"></param>
        /// <param name="mappedCrawledPropertiesElement"></param>
        /// <param name="mappedCrawledProperties"></param>
        private void CompareManagedPropertyMappedCrawledProperties(XElement updates, XElement mappedCrawledPropertiesElement, List<CrawledPropertyInfo> mappedCrawledProperties)
        {
            StringBuilder elementText = new StringBuilder();
            if (mappedCrawledPropertiesElement != null)
                foreach (XElement element in mappedCrawledPropertiesElement.Nodes())
                    elementText.AppendFormat("name={0},category={1};", element.Attribute("name").Value, element.Attribute("category").Value);
            StringBuilder text = new StringBuilder();
            if (mappedCrawledProperties != null)
                foreach (CrawledPropertyInfo item in mappedCrawledProperties)
                    text.AppendFormat("name={0},category={1};", item.Name, item.CategoryName);
            if (mappedCrawledPropertiesElement == null && mappedCrawledProperties != null && mappedCrawledProperties.Count > 0)
                mappedCrawledPropertiesElement = new XElement("crawledProperties");
            AddUpdateElement(updates, mappedCrawledPropertiesElement, text, elementText);
        }
        #endregion

    }
}